import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from './decorators/public.decorators';
import { RedisService } from 'libs/db/src';
import { ExtractJwt } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';

/**
 * 权限守卫类，用于判断用户是否有权限访问
 */
@Injectable()
export class AuthGuard implements CanActivate {
  // 构造函数，注入JwtService和Reflector服务
  constructor(
    private readonly configService: ConfigService,
    private readonly jwtService: JwtService,
    private reflector: Reflector,
    private readonly redisService: RedisService
  ) {}

  /**
   * 实现CanActivate接口的canActivate方法，用于判断用户是否有权限访问
   * @param ctx 请求上下文
   * @returns
   */
  async canActivate(ctx: ExecutionContext): Promise<boolean> {
    const JWT_SECRET = this.configService.get<string>('JWT_SECRET');
    const JWT_EXPIRES_IN = this.configService.get<number>('JWT_EXPIRES_IN');

    // 1、获取请求处理函数和请求类的IS_PUBLIC_KEY装饰器值
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      ctx.getHandler(),
      ctx.getClass()
    ]);
    // 2、如果IS_PUBLIC_KEY装饰器值为true，则表示该请求不需要认证，直接返回true
    if (isPublic) {
      return true;
    }
    // 3、获取请求对象
    const request = ctx.switchToHttp().getRequest<Request>();
    // 从请求头中提取token
    const token = this.extractTokenFromHeader(request);
    // 如果没有token，则抛出未授权异常
    if (!token) {
      throw new UnauthorizedException('没有token令牌或token失效！拒绝访问。');
    }

    // 4、验证token的有效性，并将payload赋值给request.user
    try {
      // 验证token的有效性，并将payload赋值给request.user
      // const payload = await this.jwtService.verifyAsync(token, {
      //   secret: process.env.JWT_SECRET
      // });
      // console.log('payload:', payload);

      // request.user = payload;
      const tokenRequest = ExtractJwt.fromAuthHeaderAsBearerToken()(request);
      // console.log('tokenRequest:', tokenRequest);
      const payload = await this.jwtService.verifyAsync(tokenRequest, {
        secret: JWT_SECRET
      });
      request['user'] = payload;
      // console.log('request:', request.user);

      const cacheToken = await this.redisService.get(`token_${payload._id}`);
      if (!cacheToken)
        throw new UnauthorizedException('token已过期失效！拒绝访问。');
      if (!tokenRequest || tokenRequest !== cacheToken)
        throw new UnauthorizedException(
          '没有token令牌或token不正确！拒绝访问。'
        );
      // 将token存入redis，设置过期时间
      await this.redisService.set(
        `token_${payload._id}`,
        token,
        JWT_EXPIRES_IN
      );
    } catch (e) {
      // 如果验证失败，则抛出未授权异常
      throw new UnauthorizedException(e);
    }
    // 返回true，表示用户有权限访问
    return true;
  }

  // 从请求头中提取token的方法
  private extractTokenFromHeader(request: Request): string | undefined {
    // 从请求头中获取authorization字段的值，并按空格分割
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    // 如果type为Bearer，则返回token，否则返回undefined
    return type === 'Bearer' ? token : undefined;
  }
}
